package com.content.scaffold.common.dingtalk;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.content.scaffold.common.exception.BaseException;
import com.content.scaffold.common.web.RequestContext;
import com.content.scaffold.common.web.RequestInfo;
import com.content.scaffold.common.web.ResponseCodeEnum;
import lombok.Builder;
import lombok.NonNull;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.logging.log4j.core.util.UuidUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.util.ClassUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;

/**
 * @author xiaojl
 */
@Builder
@Slf4j
public class DingTalkRobot {

    private static final boolean SKYWALKING_TRACE_CONTENT_PRESENT = ClassUtils.isPresent("org.apache.skywalking.apm.toolkit.trace.TraceContext", null);

    @NonNull
    private String secret;
    @NonNull
    private String accessToken;
    @Builder.Default
    private String baseUrl = "https://oapi.dingtalk.com/robot/send";
    @Builder.Default
    private String title = "异常提醒";
    @Builder.Default
    private int timeout = 10_000;
    @Builder.Default
    private boolean showDetails;

    private ExecutorService executorService;

    public DingTalkRobotMessage newMessage() {
        return new DingTalkRobotMessage().currentTraceId();
    }

    private void push(DingTalkRobotMessage message) throws Exception {
        String url = buildFullRequest();
        String body = formatRobotMessage(message);
        String response = HttpUtil.post(url, body, timeout);
        log.debug("钉钉异常提醒, 推送返回结果: {}", response);
    }

    private void pushAsync(DingTalkRobotMessage message) {
        DingTalkMessagePushTask task = new DingTalkMessagePushTask(message);
        if (executorService != null) {
            executorService.submit(task);
        } else {
            log.debug("no executor service, run in current thread");
            task.run();
        }
    }

    private String formatRobotMessage(DingTalkRobotMessage message) {
        Map<String, Object> result = new HashMap<>();
        result.put("msgtype", "markdown");
        result.put("markdown", formatAsMarkdown(message));
        return JSONUtil.toJsonStr(result);
    }

    private Map<String, Object> formatAsMarkdown(DingTalkRobotMessage message) {
        if (BooleanUtil.isTrue(message.msg)) {
            return setContentByMsg(message);
        }
        return setContentByError(message);
    }

    private Map<String, Object> setContentByMsg(DingTalkRobotMessage message) {
        Map<String, Object> content = new HashMap<>(2);

        // 避免副作用：将 title 明确为局部变量
        String title = StringUtils.isNotEmpty(message.title) ? message.title : this.title;
        content.put("title", title);

        StringBuilder textBuilder = new StringBuilder("### ").append(title).append("\n");

        if (StringUtils.isNotEmpty(message.requestId)) {
            textBuilder.append("* 请求ID: ").append(text(message.requestId)).append("\n");
        }

        boolean hasErrorUrl = StringUtils.isNotEmpty(message.errorUrl);
        if (hasErrorUrl) {
            textBuilder.append("* 请求路径: ").append(text(message.errorUrl)).append("\n");
        }
        if (message.params != null) {
            textBuilder.append("* 请求参数: ").append(text(message.params)).append("\n");
        }
        if (message.content != null) {
            textBuilder.append("* 消息描述: ").append(message.content).append("\n");
        }


        // 使用线程安全且高效的日期格式化工具
        String currentTime = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
        textBuilder.append("* 请求时间: ").append(currentTime).append("\n");
        content.put("text", textBuilder.toString());
        return content;
    }

    @NotNull
    private Map<String, Object> setContentByError(DingTalkRobotMessage message) {
        Throwable error = message.error;
        RequestInfo reqInfo = message.requestInfo == null ? RequestContext.buildAnonymous() : message.requestInfo;
        String errorMessage = error != null ? error.getClass().getName() + ": " + error.getMessage() : "";
        String fullErrorStack = error != null ? ExceptionUtils.getStackTrace(error) : "";
        String traceId = message.traceId;
        String localTitle = StringUtils.isNotEmpty(message.title) ? message.title : title;

        StringBuilder o = new StringBuilder()
                .append("### ").append(localTitle).append("\n")
                // .append("* 请求ID: ").append(text(reqInfo.getRequestId())).append("\n")
                .append("* TraceId: ").append(text(traceId)).append("\n")
                .append("* 应用ID: ").append(text(reqInfo.getOfficeId())).append("\n")
                .append("* 请求路径: ").append(text(message.errorUrl)).append("\n")
                .append("* 请求用户: ").append(text(reqInfo.getUserId())).append("\n")
                //  .append("* 请求IP: ").append(text(reqInfo.get())).append("\n")
                .append("* 异常时间: ").append(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss")).append("\n")
                .append("* 异常描述: ").append(errorMessage).append("\n");
        if (BooleanUtil.isTrue(message.showDetail) && StringUtils.isNotBlank(fullErrorStack)) {
            o.append("```\n").append(fullErrorStack).append("\n");
        }
        Map<String, Object> content = new HashMap<>();
        content.put("title", title);
        content.put("text", o.toString());
        return content;
    }

    private String text(String value) {
        return value == null ? "" : value;
    }

    private String buildFullRequest() throws Exception {
        long timestamp = System.currentTimeMillis();
        return baseUrl +
                "?access_token=" + accessToken +
                "&timestamp=" + timestamp +
                "&sign=" + sign(timestamp, secret);
    }

    // 生成签名
    public static String sign(long timestamp, String secret) throws Exception {
        if (StringUtils.isBlank(secret)) {
            throw new BaseException(ResponseCodeEnum.ERROR, "secret not allow blank");
        }
        byte[] secretBuf = secret.getBytes(StandardCharsets.UTF_8);
        SecretKeySpec keySpec = new SecretKeySpec(secretBuf, "HmacSHA256");

        String text = timestamp + "\n" + secret;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(keySpec);
        byte[] sign = mac.doFinal(text.getBytes(StandardCharsets.UTF_8));
        return URLEncoder.encode(Base64.getEncoder().encodeToString(sign), StandardCharsets.UTF_8.name());
    }

    private class DingTalkMessagePushTask implements Runnable {

        private DingTalkRobotMessage message;

        DingTalkMessagePushTask(DingTalkRobotMessage message) {
            this.message = message;
        }

        @Override
        public void run() {
            try {
                DingTalkRobot.this.push(message);
            } catch (Exception e) {
                log.error("钉钉异步推送消息失败", e);
            }
        }

    }

    @Accessors(chain = true)
    @Setter
    public class DingTalkRobotMessage {

        private String errorUrl;
        private Throwable error;
        private RequestInfo requestInfo;
        private String traceId;
        private Boolean msg;
        private String content;
        private String title;
        private String requestId;
        private String params;
        private Boolean showDetail;

        public void push() throws Exception {
            try {
                DingTalkRobot.this.push(this);
            } catch (Exception e) {
                log.error("发送钉钉机器人消息失败", e);
                throw e;
            }
        }

        public void pushAsync() {
            DingTalkRobot.this.pushAsync(this);
        }

        private DingTalkRobotMessage currentTraceId() {
            if (SKYWALKING_TRACE_CONTENT_PRESENT) {
                return setTraceId(getSkywalkingTraceId());
            }
            return this;
        }

    }

    private static String getSkywalkingTraceId() {
        return UuidUtil.getTimeBasedUuid().toString();
    }
}


